//    Copyright (C) Kherty.  All rights reserved.
using System.Collections.Generic;
using System.Diagnostics.Contracts;

namespace OpenLS.Spreadsheet
{
    internal class CalculationChain
    {
        private readonly IDependencyNode _rootNode;
        // NodeList affectedNodes;
        private List<IDependencyNode> _chain;
        private List<IDependencyNode> _cycle;

        public CalculationChain(IDependencyNode cell)
        {
            _rootNode = cell;
            build(cell);
        }

        public CalculationChain(IEnumerable<IDependencyNode> nodes)
        {
            buildChainMultiple(new DependencyBuilder(nodes).AffectedNodes);
        }

        private void build(IDependencyNode node)
        {
            buildChain(new DependencyBuilder(node).AffectedNodes);
        }

        private void buildChain(IEnumerable<IDependencyNode> affectedNodes)
        {
            var graph = new Graph<IDependencyNode>();
            foreach (IDependencyNode node in affectedNodes)
                graph.AddData(node);
            foreach (IDependencyNode node in affectedNodes)
                foreach (IDependencyNode dep in node.Dependents())
                    graph.AddEdge(graph[node], graph[dep]);

            List<Node<IDependencyNode>> orderedNodes = graph.SortAndRemove();
            if (graph.Count > 0)
            {
                foreach (var node in graph.GetNodes())
                {
                    if (node.Data == _rootNode)
                        throw new CircularReferenceException();
                }
                _cycle = new List<IDependencyNode>();
                foreach (var node in graph.GetNodes())
                    _cycle.Add(node.Data);
            }
            _chain = new List<IDependencyNode>();
            foreach (var node in orderedNodes)
                _chain.Add(node.Data);
        }

        private void buildChainMultiple(IEnumerable<IDependencyNode> affectedNodes)
        {
            var graph = new Graph<IDependencyNode>();
            foreach (IDependencyNode node in affectedNodes)
                graph.AddData(node);
            foreach (IDependencyNode node in affectedNodes)
                foreach (IDependencyNode dep in node.Dependents())
                    graph.AddEdge(graph[node], graph[dep]);

            List<Node<IDependencyNode>> orderedNodes = graph.SortAndRemove();
            if (graph.Count > 0)
            {
                foreach (var node in graph.GetNodes())
                {
                    if (node.Data == _rootNode)
                        throw new CircularReferenceException();
                }
                _cycle = new List<IDependencyNode>();
                foreach (var node in graph.GetNodes())
                    _cycle.Add(node.Data);
            }
            _chain = new List<IDependencyNode>();
            foreach (var node in orderedNodes)
                _chain.Add(node.Data);
        }

        public void Calculate()
        {
            foreach (IDependencyNode c in _chain)
                c.ChainRecalc();
            if (_cycle != null)
            {
                var counter = new Dictionary<IDependencyNode, int>();
                Graph<IDependencyNode> graph = createGraph(_cycle);
                var queue = new Queue<IDependencyNode>(_cycle);
                while (queue.Count > 0)
                {
                    IDependencyNode node = queue.Dequeue();
                    if (!counter.ContainsKey(node))
                    {
                        counter[node] = 1;
                    }
                    else
                    {
                        counter[node] = counter[node] + 1;
                    }
                    if (counter[node] > 100)
                        continue;
                    bool b = node.ProcessChange();
                    if (b)
                    {
                        Node<IDependencyNode> cellNode = graph[node];
                        foreach (var n in cellNode.Children)
                            queue.Enqueue(n.Data);
                    }
                }
            }
        }

        private static Graph<IDependencyNode> createGraph(IEnumerable<IDependencyNode> cycle)
        {
            var result = new Graph<IDependencyNode>();
            var toProcess = new Queue<IDependencyNode>(cycle);
            while (toProcess.Count > 0)
            {
                IDependencyNode c = toProcess.Dequeue();
                if (result.Contains(c))
                    continue;
                result.AddData(c);
                foreach (IDependencyNode dependent in c.Dependents())
                    toProcess.Enqueue(dependent);
            }
            return result;
        }
    }
}